package _.mail.javax;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.UIDFolder;

import org.budo.support.lang.util.ArrayUtil;
import org.budo.support.lang.util.StringUtil;

import _.javax.mail.FolderUtil;
import _.javax.mail.StoreUtil;
import b.async.Handler;
import lombok.extern.slf4j.Slf4j;

import org.budo.mail.exception.AuthenticationFailedRuntimeException;
import org.budo.mail.javax.BudoJavaxReceiveMail;
import org.budo.mail.javax.ReceiveMail;

/**
 * http://www.yiibai.com/javamail_api/javamail_api_imap_servers.html
 * 
 * @author li
 * @version 1 (2014年7月4日 下午3:34:40)
 */
@Slf4j
public class JavaxMailReceiverImpl extends AbstractJavaxMailReceiver {
    public void receive(Handler handler) {
        try {
            boolean checkConfig = this.checkConfig();
            if (checkConfig) {
                this.doReceive(handler);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void doReceive(Handler<ReceiveMail> handler) {
        Store store = null;
        Folder inbox = null;

        try {
            Session session = this.preapreSession();
            store = this.connectStore(session, this.getProtocol(), this.getHost(), this.getPort(), this.getUsername(), this.getPassword(), this.getSsl());
            if (null == store) {
                log.warn("#54 JavaxMailReceiver doReceive() store is null, this=" + this);
                return;
            }

            int mode = this.getMode();
            inbox = this.storeGetFolder(store, "INBOX");
            this.folderOpen(mode, inbox);

            log.debug("#61 opened inbox {}, mode={}, 未读邮件 {} 封, 新邮件 {} 封, 已删除邮件 {} 封", //
                    this.getUsername(), FolderUtil.modeToString(mode), //
                    inbox.getUnreadMessageCount(), //
                    inbox.getNewMessageCount(), //
                    inbox.getDeletedMessageCount());

            FetchProfile profile = new FetchProfile();
            profile.add(UIDFolder.FetchProfileItem.UID);

            Message[] messages = inbox.getMessages();
            log.debug("{} messages to be fetch in {}", messages.length, getUsername());
            inbox.fetch(messages, profile);

            this.doHandleMessages(handler, messages);
        } catch (Throwable e) {
            String error = "" + e;

            if (StringUtil.contains(error, "javax.mail.MessagingException: A3 NO SELECT The login is not safe! Please update your mail client: http://mail.163.com/dashi;")) {
                log.error("doReceive() store error 1, e={}" + e);
            } else {
                log.error("doReceive() store error 2, e=" + e, e);
            }
        } finally {
            StoreUtil.closeInbox(inbox, this.getDeleteMails());
            StoreUtil.closeStore(store);
        }

        log.debug("结束接收并处理邮件 inbox.close(deleteMails=" + getDeleteMails() + ") && store.close()");
    }

    public void doHandleMessages(Handler<ReceiveMail> handler, Message[] messages) {
        if (ArrayUtil.isEmpty(messages)) {
            return;
        }

        log.info("#96 JavaxMailReceiver fetched Messages {}, 开始接收并处理邮件", this.getUsername());

        for (int i = messages.length - 1; i >= 0; i--) {
            Message message = messages[i];
            ReceiveMail receiveMail = new BudoJavaxReceiveMail(message);
            try {
                this.doHandleMessage(handler, receiveMail);
            } catch (Throwable e) {
                log.error("#104 JavaxMailReceiver doHandleMessage error ", e);
            }
        }
    }

    public void doHandleMessage(Handler handler, org.budo.mail.javax.ReceiveMail receiveMail) {
        handler.handle(receiveMail);
    }

    public Store connectStore(Session session, String protocol, String host, Integer port, String username, String password, Boolean ssl) throws Exception {
        if (null == getConnectStoreTimeout() || getConnectStoreTimeout() < 0) {
            return this.doConnectStore(session, protocol, host, port, username, password, ssl);
        } else {
            return this.localTimeoutConnectStore(session, protocol, host, port, username, password, ssl);
        }
    }

    /**
     * 与验证邮件渠道配置时的连接方式不同
     * 
     * @param ssl
     * @throws Exception
     * @see _.mail.util.MailServerUtil#popMailServerAvailable(String, String,
     *      String, String, boolean, String)
     */
    public Store doConnectStore(Session session, String protocol, String host, Integer port, String username, String password, Boolean ssl) {
        String message = "#121 protocol=" + protocol //
                + ", host=" + host //
                + ", port=" + port //
                + ", username=" + username //
                + ", ssl=" + ssl;

        if ("POP3".equalsIgnoreCase(protocol)) { // POP 协议使用验证邮件渠道时候的方式连接
            Store store = null;
            try {
                store = StoreUtil.getStore(protocol, host, username, password, ssl, port);
                store = StoreUtil.connect(store, username, password);

                if (!store.isConnected()) {
                    throw new IllegalStateException("pop3 store connect fail, message=" + message);
                }

                return store;
            } catch (Throwable e) {
                String error = e + "";
                if (StringUtil.contains(error, "Unrecognized SSL message, plaintext connection?")) {
                    log.warn("JavaxMailReceiver doConnectStore error 4, session=" + session + ", " + message + ", e=" + e);
                } else if (StringUtil.contains(error, "javax.mail.MessagingException: Connect failed") || StringUtil.contains(error, "java.net.ConnectException: Connection timed out")) {
                    log.error("JavaxMailReceiver doConnectStore error 5, session=" + session + ", " + message + ", e=" + e);
                } else if (StringUtil.contains(error, "javax.mail.AuthenticationFailedException:")) {
                    log.warn("JavaxMailReceiver doConnectStore error 6, session=" + session + ", " + message + ", e=" + e);
                } else {
                    log.error("JavaxMailReceiver doConnectStore error 7, session=" + session + ", " + message, e);
                }

                StoreUtil.closeStore(store);
                return null;
            }
        } else {
            return StoreUtil.connect(session, protocol, host, port, username, password);
        }
    }

    public Store localTimeoutConnectStore(final Session session, final String protocol, final String host, final Integer port, final String username, final String password, final Boolean ssl) {
        ExecutorService executorService = null;
        FutureTask<Store> futureTask = null;
        Store store = null;
        try {
            executorService = Executors.newSingleThreadExecutor();

            futureTask = new FutureTask<Store>(new Callable<Store>() {
                public Store call() throws Exception {
                    Store connectdStore = null;
                    try {
                        log.debug("#166 JavaxMailReceiver start futureTask connectStoreTimeout=" + getConnectStoreTimeout() + ", this=" + this //
                                + ", protocol=" + protocol//
                                + ", host=" + host //
                                + ", port=" + port //
                                + ", username=" + username //
                                + ", ssl=" + JavaxMailReceiverImpl.this.getSsl());

                        connectdStore = JavaxMailReceiverImpl.this.doConnectStore(session, protocol, host, port, username, password, ssl);
                        return connectdStore;
                    } catch (Throwable e) {
                        String error = e + "";
                        if (StringUtil.contains(error, "java.net.SocketTimeoutException: Read timed out")) { // 超时
                            log.error("JavaxMailReceiver doConnectStore error 1, connectdStore=" + connectdStore + " ,e=" + e);
                        } else if (StringUtil.contains(error, "javax.mail.AuthenticationFailedException:")) { // 密码错误
                            log.error("JavaxMailReceiver doConnectStore error 2, connectdStore=" + connectdStore + " ,e=" + e);
                        } else {
                            log.error("JavaxMailReceiver doConnectStore error 3, connectdStore=" + connectdStore + " ,e=" + e, e);
                        }

                        StoreUtil.closeStore(connectdStore);
                        return null;
                    }
                }
            });
            executorService.submit(futureTask);
            store = futureTask.get(getConnectStoreTimeout(), TimeUnit.SECONDS);
            return store;
        } catch (TimeoutException e) {
            futureTask.cancel(true); // 停止任务
            executorService.shutdownNow(); // 停止线程
            log.error("JavaxMailReceiver connectStore timeout connectStoreTimeout=" + this.getConnectStoreTimeout() //
                    + ", this=" + this //
                    + ", protocol=" + protocol //
                    + ", host=" + host //
                    + ", port=" + port //
                    + ", username=" + username //
                    + ", ssl=" + this.getSsl());
            return null;
        } catch (AuthenticationFailedRuntimeException e) {
            if (null != futureTask) {
                futureTask.cancel(true);
            }
            if (null != executorService) {
                executorService.shutdownNow();
            }
            this.whenAuthenticationFailed(e);
            return null;
        } catch (Throwable e) {
            if (null != futureTask) {
                futureTask.cancel(true);
            }
            if (null != executorService) {
                executorService.shutdownNow();
            }
            log.error("#219 connectStore error connectStoreTimeout=" + this.getConnectStoreTimeout() //
                    + ", this=" + this //
                    + ", protocol=" + protocol //
                    + ", host=" + host //
                    + ", port=" + port //
                    + ", username=" + username //
                    + ", ssl=" + this.getSsl(), e);
            return null;
        } finally {
            if (null != executorService && !executorService.isShutdown()) {
                executorService.shutdown();
            }
        }
    }

    /**
     * 每隔10次打异常堆栈到日志
     */
    protected void whenAuthenticationFailed(AuthenticationFailedRuntimeException e) {
        Integer connectStoreTimeout = getConnectStoreTimeout();
        String protocol = getProtocol();
        String host = getHost();
        Integer port = getPort();
        String username = getUsername();
        Boolean ssl = getSsl();

        log.error("JavaxMailReceiver AuthenticationFailedRuntimeException, this=" + this + ", connectStoreTimeout=" + connectStoreTimeout + ", protocol=" + protocol + ", host=" + host + ", port=" + port + ", username=" + username + ", ssl=" + ssl, e);
    }
}